package token

import (
	"context"
	"gf_blog_admin/internal/model"
	"gf_blog_admin/internal/service"
	"gf_blog_admin/utility/errors"
	"github.com/gogf/gf/v2/crypto/gmd5"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gcache"
	"github.com/gogf/gf/v2/os/gctx"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/golang-jwt/jwt/v5"
	"strconv"
	"strings"
	"time"
)

type sToken struct {
	config *model.TokenOptions
}

func init() {
	service.RegisterToken(New())
}

func New() *sToken {
	var (
		ctx  = gctx.GetInitCtx()
		conf *model.TokenOptions
	)
	if err := g.Cfg().MustGet(ctx, "token").Struct(&conf); err != nil {
		g.Log().Error(ctx, err)
		panic(err.Error())
	}
	return &sToken{config: conf}
}

// GenerateToken 生成token
func (s *sToken) GenerateToken(ctx context.Context, user *model.AdminClaims) (token string, expires int64, err error) {
	expires = s.config.Expires
	duration := time.Second * gconv.Duration(expires)
	token, err = s.createToken(model.JwtClaims{
		AdminClaims: user,
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    "blog_admin",
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(duration)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			NotBefore: jwt.NewNumericDate(time.Now()),
		},
	})
	if err != nil {
		return "", 0, err
	}
	if !s.config.MultiLogin {
		tokenKey := s.getCacheKey(user.UserId, user.Device)
		err = gcache.Set(ctx, tokenKey, s.enToken(token), duration)
	}
	return
}

// RefreshToken 刷新token
func (s *sToken) RefreshToken(ctx context.Context, oldToken string) (token string, expires int64, err error) {
	claims, er := s.ParseTokenClaims(ctx, oldToken)
	if er != nil {
		return "", 0, er
	}
	expires = s.config.Expires
	duration := time.Second * gconv.Duration(expires)
	claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(duration))
	token, err = s.createToken(*claims)
	if err != nil {
		return "", 0, err
	}
	return
}

// RemoveToken 删除token
func (s *sToken) RemoveToken(ctx context.Context, tokenString string) error {
	claims, err := s.ParseTokenClaims(ctx, tokenString)
	if err != nil {
		return err
	}
	tokenKey := s.getCacheKey(claims.UserId, claims.Device)
	if token, er := gcache.Get(ctx, tokenKey); er != nil {
		return gerror.NewCode(errors.CodeUserLogin)
	} else if token.String() != s.enToken(tokenString) {
		return gerror.NewCode(errors.CodeUserMultiLogin)
	}
	_, err = gcache.Remove(ctx, tokenKey)
	return err
}

// IsValid 检查token有效性
func (s *sToken) IsValid(ctx context.Context, uid uint64, device, tokenString string) error {
	// 是否允许多端登录
	if !s.config.MultiLogin {
		token, err := gcache.Get(ctx, s.getCacheKey(uid, device))
		if err != nil {
			return gerror.NewCode(errors.CodeUserLogin)
		}
		if token.String() != s.enToken(tokenString) {
			return gerror.NewCode(errors.CodeUserMultiLogin)
		}
	}
	return s.IsBlackValid(ctx, tokenString)
}

// JoinBlack 加入黑名单
func (s *sToken) JoinBlack(ctx context.Context, tokenString string) (err error) {
	duration := time.Second * gconv.Duration(s.config.Expires)
	if tokenString == "" {
		tokenString = s.GetRequestToken(ctx)
	}
	blackKey := s.getBlackKey(tokenString)
	err = gcache.Set(ctx, blackKey, time.Now().Unix(), duration)
	return
}

// IsBlackValid 检查token是否在黑名单
func (s *sToken) IsBlackValid(ctx context.Context, tokenString string) error {
	if revoked, err := gcache.Get(ctx, s.getBlackKey(tokenString)); err != nil {
		return gerror.NewCode(errors.CodeUserLogin)
	} else if revoked.Bool() {
		return gerror.NewCode(errors.CodeTokenExpired)
	}
	return nil
}

// ParseTokenClaims 解析jwt令牌
func (s *sToken) ParseTokenClaims(ctx context.Context, tokenString string) (*model.JwtClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &model.JwtClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(s.config.Secret), nil
	})
	if err != nil {
		return nil, err
	}
	if claims, ok := token.Claims.(*model.JwtClaims); ok && token.Valid {
		return claims, nil
	}
	return nil, jwt.ErrSignatureInvalid
}

// GetRequestToken 获取请求体携带的token
func (s *sToken) GetRequestToken(ctx context.Context) (token string) {
	// 请求头获取
	var (
		prefix = "Bearer "
		n      = len(prefix)
	)
	auth := g.RequestFromCtx(ctx).Header.Get("Authorization")
	if len(auth) >= n && auth[:n] == prefix {
		return auth[n:]
	}
	// 查询参数
	if q := g.RequestFromCtx(ctx).Get("token"); !q.IsEmpty() {
		return q.String()
	}
	// Cookies
	if c := g.RequestFromCtx(ctx).Cookie.Get("token"); !c.IsEmpty() {
		return c.String()
	}
	return
}

func (s *sToken) createToken(claims model.JwtClaims) (string, error) {
	// 生成jwt格式的header、claims 部分
	tokenPartA := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	// 继续添加秘钥值，生成最后一部分
	return tokenPartA.SignedString([]byte(s.config.Secret))
}

// 令牌缓存key
func (s *sToken) getCacheKey(uid uint64, device string) string {
	str := strings.Replace(s.config.CacheKey, "{uid}", strconv.FormatUint(uid, 10), 1)
	return strings.Replace(str, "{device}", device, 1)
}

// 黑名单缓存key
func (s *sToken) getBlackKey(token string) string {
	return strings.Replace(s.config.BlackKey, "{token}", s.enToken(token), 1)
}

// 加密token
func (s *sToken) enToken(tokenString string) string {
	return gmd5.MustEncryptString(tokenString)
}
